package org.nanhill.commons.lang;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Just for JDK1.5+
 * 
 * A utils for reflect.
 * 
 * @author alin
 * 
 */
public class ReflectKit {
	/**
	 * Notice: the performance of this method is bad, please use method cache
	 * instead
	 * 
	 * @param cls
	 * @param methodName
	 * @return
	 * @throws NoSuchMethodException
	 */
	public static Method getOnePublicMethod(Class cls, String methodName)
			throws NoSuchMethodException {
		Method[] methods = cls.getMethods();
		for (int i = 0; i < methods.length; i++) {
			Method item = methods[i];
			if (methodName.equals(item.getName())) {
				return item;
			}
		}
		throw new NoSuchMethodException();
	}

	/**
	 * Get the benmost class of input <code>java.util.Collection</code> field.
	 * For example: if input field type is <code>List<List<String>></code>,
	 * this method return class <code>java.lang.String</code>
	 * 
	 * @param field
	 *            input field
	 * @return the benmost class of input field if it is a collection field,
	 *         otherwise null
	 */
	public static Class getCollectionFieldBenmostClass(Field field) {
		if (isCollection(field.getType())) {
			ParameterizedType ptype = (ParameterizedType) field
					.getGenericType();
			while (true) {
				Type genericType = (ptype.getActualTypeArguments())[0];
				if (genericType.getClass() == Class.class) {
					return (Class) genericType;
				}
				ptype = (ParameterizedType) genericType;
			}
		}
		return null;
	}

	public static Type getCollectionGenericType(Field field) {
		if (isCollection(field.getType())) {
			return getParameterizedTypes(field.getGenericType())[0];
		}
		return null;
	}

	public static Type[] getParameterizedTypes(Type type) {
		if (Class.class == type.getClass()) {
			return null;
		}
		ParameterizedType ptype = (ParameterizedType) type;
		return ptype.getActualTypeArguments();
	}

	public static boolean isPrimitiveType(Class cl) {
		if (cl == char.class)
			return true;
		if (cl == boolean.class)
			return true;
		if (cl == int.class)
			return true;
		if (cl == byte.class)
			return true;
		if (cl == long.class)
			return true;
		if (cl == double.class)
			return true;
		if (cl == float.class)
			return true;
		if (cl == short.class)
			return true;
		return false;
	}

	public static boolean isPrimitiveWrapType(Class cl) {
		if (cl == Character.class)
			return true;
		if (cl == Boolean.class)
			return true;
		// check 6 number primitive type wrap
		if (Number.class.isAssignableFrom(cl)) {
			return true;
		}
		return false;
	}

	public static boolean isPrimitiveOrWrapType(Class cl) {
		return isPrimitiveWrapType(cl) || isPrimitiveType(cl);
	}

	public static boolean isMap(Class cl) {
		return Map.class.isAssignableFrom(cl);
	}

	public static boolean isType(Class cl) {
		return Type.class.isAssignableFrom(cl);
	}

	/**
	 * check the input class is jdk default parameterized type
	 * sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
	 * 
	 * @param cl
	 * @return
	 */
	public static boolean isDefaultParameterizedType(Class cl) {
		return sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.class == cl;
	}

	public static boolean isDefaultGenericArrayType(Class cl) {
		return sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl.class == cl;
	}

	public static boolean isCollection(Class cl) {
		return Collection.class.isAssignableFrom(cl);
	}

	public static String getCurrentMethodName() {
		// String methodName = null;
		// try {
		// throw new Exception();
		// } catch (Exception e) {
		// methodName = e.getStackTrace()[1].getMethodName();
		// }
		// return methodName;
		return new Exception().getStackTrace()[1].getMethodName();
	}

	public static String where() {
		StackTraceElement[] elems = new Exception().getStackTrace();
		return (elems.length < 2) ? "" : elems[1].getFileName() + "@"
				+ elems[1].getLineNumber() + ":" + elems[1].getClassName()
				+ "." + elems[1].getMethodName() + "()";
	}

	/**
	 * return all accessible field except static and transient
	 * 
	 * @param obj
	 * @return
	 */
	public static List<Field> getAllAccessibleField(Object obj) {
		final List<Field> fieldList = new ArrayList<Field>();
		Class cl = obj.getClass();
		for (; cl != null; cl = cl.getSuperclass()) {
			Field[] fields = cl.getDeclaredFields();
			for (int i = 0; i < fields.length; i++) {
				Field field = fields[i];
				if (Modifier.isTransient(field.getModifiers())
						|| Modifier.isStatic(field.getModifiers()))
					continue;
				field.setAccessible(true);
			}
			fieldList.addAll(Arrays.asList(fields));
		}
		return fieldList;
	}
}
